/*****************************************************************
Copyright (c) 2011 - 2017 Qualcomm Technologies International, Ltd.

PROJECT
    source
    
FILE NAME
    source_app_msg_handler.c

DESCRIPTION
    Application message handler
    
*/


/* header for this file */
#include "source_app_msg_handler.h"
/* application header files */
#include "source_a2dp.h"
#include "source_avrcp.h"
#include "source_debug.h"
#include "source_init.h"
#include "source_inquiry.h"
#include "source_power.h" 
#include "source_scan.h"
#include "source_states.h"
#include "source_usb.h"
#include "source_aghfp.h"
#include "source_ps.h"
#include "source_audio.h"
#include "source_led_handler.h"
#include "source_connection_mgr.h"
#include "source_button_handler.h"
#include "source_memory.h"
#include "power_manager_conditions.h"
#include "source_volume.h"
#include "state_of_charge.h"
#include "power_manager.h"
#include "charger_monitor.h"
#include "battery_monitor.h"
#include <system_reboot.h>
#include <connection.h>
#include <boot.h>
#include <usb.h>
#include <logging.h>
/* VM headers */
#include <psu.h>


#ifdef DEBUG_APP_MSG
    #define APP_MSG_DEBUG(x) SOURCE_DEBUG(x)
#else
    #define APP_MSG_DEBUG(x)
#endif


/* Display unhandled states in Debug Mode */
#define app_msg_unhandled_state() APP_MSG_DEBUG(("APP MSG Ignored; state [%d]\n", states_get_state()));


/* Application message handling functions */
static void app_init_cfm(void);
static void app_connect_success_cfm(void);
static void app_connect_fail_cfm(void);
static void app_disconnect_ind(const APP_DISCONNECT_IND_T *ind);
static void app_connect_req(const APP_CONNECT_REQ_T *message);
static void app_disconnect_all_req(void);
static void app_disconnect_signalling_req(void);
static void app_linkloss_ind(void);
static void app_discovery_state_timeout(void);
static void app_inquiry_continue(void);
static void app_enter_pairing_state_from_idle(void);
static void app_enter_connectable_state_from_idle(void);
static void app_usb_audio_active(void);
static void app_usb_audio_inactive(void);
static void app_mic_audio_active(void);
static void app_mic_audio_inactive(void);
static void app_factory_reset(void);
static void app_a2dp_media_connected(bool codec_configure);
static void app_codec_swtich(void);
static void app_update_audio_routing(uint16 delay);
static void app_audio_suspend_cfm(void);
static void app_volume_up(void);
static void app_volume_down(void);
static void app_auto_power_off(void);
static void app_next_track(void);
static void app_previous_track(void);
static void app_avrcp_play(void);
static void app_avrcp_stop(void);
static void app_avrcp_toggle(void);
static void app_codec_led_update(void);
static void app_power_off_pattern_complete(void);
/***************************************************************************
Functions
****************************************************************************
*/

/*******************************************************************************
    App message handler
*/

static void app_msg_handler(Task task, MessageId id, Message message)
{
    UNUSED(task);
    switch (id)
    {
        /* confirmation that the application has been initialised */
        case APP_INIT_CFM:
        {
            DEBUG_LOG_INFO("APP_INIT_CFM");
            app_init_cfm();
        }
        break;
        
        /* confirmation that a connection attempt has successfully completed */
        case APP_CONNECT_SUCCESS_CFM:
        {
            APP_MSG_DEBUG(("APP_CONNECT_SUCCESS_CFM\n"));
            app_connect_success_cfm();
        }
        break;
        
        /* confirmation that a connection attempt has failed */
        case APP_CONNECT_FAIL_CFM:
        {
            APP_MSG_DEBUG(("APP_CONNECT_FAIL_CFM\n"));
            app_connect_fail_cfm();
        }
        break;
        
        /* indication that a disconnection has occurred */
        case APP_DISCONNECT_IND:
        {
            APP_MSG_DEBUG(("APP_DISCONNECT_IND\n"));
            app_disconnect_ind((APP_DISCONNECT_IND_T*)message);
        }
        break;
        
        /* request to initiate connection to a remote device */
        case APP_CONNECT_REQ:
        {
            APP_MSG_DEBUG(("APP_CONNECT_REQ\n"));
            app_connect_req((APP_CONNECT_REQ_T *)message);
        }
        break;
        
        /* request to disconnect from a remote device */
        case APP_DISCONNECT_REQ:
        {
            APP_MSG_DEBUG(("APP_DISCONNECT_REQ\n"));
            app_disconnect_all_req();
        }
        break;

        /* request to disconnect all signalling connections */
        case APP_DISCONNECT_SIGNALLING_REQ:
        {
            APP_MSG_DEBUG(("APP_DISCONNECT_SIGNALLING_REQ\n"));
            app_disconnect_signalling_req();
        }
        break;
        
        /* indication that link loss occurred with a remote device  */
        case APP_LINKLOSS_IND:
        {
            APP_MSG_DEBUG(("APP_LINKLOSS_IND\n"));   
            app_linkloss_ind();
        }
        break;
        
        /* message that indicates that inquiry has timed out */
        case APP_INQUIRY_STATE_TIMEOUT:
        {
            APP_MSG_DEBUG(("APP_INQUIRY_STATE_TIMEOUT\n"));  
            if (inquiry_get_inquiry_data())
            {
                inquiry_set_inquiry_state_timeout(1);
            }
        }
        break;

        case APP_INQUIRY_START:
        {
            inquiry_start_discovery();
        }
        break;
        
        /* message that indicates that the discoverable state timer has expired  */
        case APP_DISCOVERY_STATE_TIMEOUT:
        {
            APP_MSG_DEBUG(("APP_DISCOVERY_STATE_TIMEOUT\n"));  
            app_discovery_state_timeout();
        }
        break;
        /* message to continue inquiry */
        case APP_INQUIRY_CONTINUE:
        {
            APP_MSG_DEBUG(("APP_INQUIRY_CONTINUE\n"));  
            app_inquiry_continue();
        }
        break;
        
        /* message to enter pairing state */
        case APP_ENTER_PAIRING_STATE_FROM_IDLE:
        {
            APP_MSG_DEBUG(("APP_ENTER_PAIRING_STATE_FROM_IDLE\n")); 
            app_enter_pairing_state_from_idle();
        }
        break;

        case APP_AUTO_POWER_OFF:
        {
            APP_MSG_DEBUG(("APP_AUTO_POWER_OFF\n"));
            app_auto_power_off();
        }
        break;

        /* message to enter connectable state */
        case APP_ENTER_CONNECTABLE_STATE_FROM_IDLE:
        {
            APP_MSG_DEBUG(("APP_ENTER_CONNECTABLE_STATE_FROM_IDLE\n")); 
            app_enter_connectable_state_from_idle();
        }
        break;
        
        /* message to route all audio for the connections that are active */
        case APP_AUDIO_START:
        {
            APP_MSG_DEBUG(("APP_AUDIO_START\n")); 
            audio_route_all();
        }
        break;
        
        /* message to suspend all audio for the connections that are active */
        case APP_AUDIO_SUSPEND:
        {
            APP_MSG_DEBUG(("APP_AUDIO_SUSPEND\n")); 
            audio_suspend_all();
        }
        break;   
        
        case APP_AUDIO_SUSPEND_CFM:
        {
            APP_MSG_DEBUG(("APP_AUDIO_SUSPEND_CFM\n"));
            app_audio_suspend_cfm();
        }
        break;
        /* message to power on the device from the SOURCE_STATE_POWERED_OFF state */
        case APP_POWER_ON_DEVICE:
        {
            APP_MSG_DEBUG(("APP_POWER_ON_DEVICE\n")); 
            /* cancel any further queued messages */
            MessageCancelAll(app_get_instance(), APP_POWER_ON_DEVICE);
            MessageCancelAll(app_get_instance(), APP_POWER_OFF_DEVICE);

            /* move to IDLE state and connections will be handled from here */
            states_set_state(SOURCE_STATE_IDLE);
        }
        break;
        
        /* message to physically power off the device from the SOURCE_STATE_POWERED_OFF state */
        case APP_POWER_OFF_DEVICE:
        {
            APP_MSG_DEBUG(("APP_POWER_OFF_DEVICE\n")); 
            source_app_power_device(FALSE);
        }
        break;
        
        case APP_USB_AUDIO_ACTIVE:
        {
            APP_MSG_DEBUG(("APP_USB_AUDIO_ACTIVE\n")); 
            app_usb_audio_active();
        }
        break;
        
        case APP_USB_AUDIO_INACTIVE:
        {
            APP_MSG_DEBUG(("APP_USB_AUDIO_INACTIVE\n")); 
            app_usb_audio_inactive();
        }
        break;
        
        case APP_MIC_AUDIO_ACTIVE:
        {
            APP_MSG_DEBUG(("APP_MIC_AUDIO_ACTIVE\n")); 
            app_mic_audio_active();
        }
        break;
        
        case APP_MIC_AUDIO_INACTIVE:
        {
            APP_MSG_DEBUG(("APP_MIC_AUDIO_INACTIVE\n")); 
            app_mic_audio_inactive();
        }
        break;
        
        case APP_STORE_DEVICE_ATTRIBUTES:
        {
            APP_MSG_DEBUG(("APP_STORE_DEVICE_ATTRIBUTES\n"));
            connection_mgr_write_device_attributes(&((APP_STORE_DEVICE_ATTRIBUTES_T *)message)->addr,
                                       ((APP_STORE_DEVICE_ATTRIBUTES_T *)message)->attributes);
        }
        break;

        case APP_UPDATE_AUDIO_ROUTING:
        {
            APP_MSG_DEBUG(("APP_UPDATE_AUDIO_ROUTING\n"));
            app_update_audio_routing(0);
        }
        break;

        case APP_FACTORY_RESET_SOURCE:
        {
            APP_MSG_DEBUG(("APP_FACTORY_RESET_SOURCE\n"));
            app_factory_reset();
        }
        break;

        case APP_A2DP_MEDIA_CONNECTED:
        {
            APP_MSG_DEBUG(("APP_A2DP_MEDIA_CONNECTED\n"));
            app_a2dp_media_connected(((APP_A2DP_MEDIA_CONNECTED_T*)message)->codec_reconfigure);
        }
        break;

        case APP_CODEC_SWITCH:
        {
            APP_MSG_DEBUG(("APP_CODEC_SWITCH\n"));
            app_codec_swtich();
        }
        break;

        case APP_VOL_UP:
        {
            APP_MSG_DEBUG(("APP_VOL_UP\n"));
            app_volume_up();
        }
        break;

        case APP_VOL_DOWN:
        {
            APP_MSG_DEBUG(("APP_VOL_DOWN\n"));
            app_volume_down();
        }
        break;

        case APP_NEXT_TRACK:
        {
            APP_MSG_DEBUG(("APP_NEXT_TRACK\n"));
            app_next_track();
        }
        break;

        case APP_PREVIOUS_TRACK:
        {
            APP_MSG_DEBUG(("APP_PREVIOUS_TRACK\n"));
            app_previous_track();
        }
        break;

        case APP_A2DP_CHAIN_CONNECT:
        {

        }
        break;

        case APP_A2DP_CHAIN_DISCONNECT:
        {

        }
        break;

        case APP_AVRCP_PLAY:
        {
            app_avrcp_play();
        }
        break;

        case APP_AVRCP_STOP:
        {
            app_avrcp_stop();
        }
        break;

        case APP_AVRCP_TOGGLE:
        {
            app_avrcp_toggle();
        }
        break;

        case APP_SPEAKER_MUTE:
        {
            APP_MSG_DEBUG(("APP_SPEAKER_MUTE\n"));
            if(states_get_state() == SOURCE_STATE_CONNECTED && audio_get_audio_routed() != AUDIO_MEDIA_TYPE_NONE)
            {
                audio_mute_speaker(TRUE);
            }
        }
        break;

        case APP_SPEAKER_UNMUTE:
        {
            APP_MSG_DEBUG(("APP_SPEAKER_UNMUTE\n"));
            if(states_get_state() == SOURCE_STATE_CONNECTED && audio_get_audio_routed() != AUDIO_MEDIA_TYPE_NONE)
            {
                audio_mute_speaker(FALSE);
            }
        }
        break;

        case APP_AUDIO_CONNECTED_IND:
        {
            APP_MSG_DEBUG(("APP_AUDIO_CONNECTED_IND\n"));
            theSource->app_data.block_led_update_during_codec_switch = FALSE;
            app_codec_led_update();
        }
        break;

        case APP_AUDIO_DISCONNECTED_IND:
        {
            APP_MSG_DEBUG(("APP_AUDIO_DISCONNECTED_IND\n"));
            app_codec_led_update();
        }
        break;

        case APP_CODEC_LED_UPDATE:
        {
            APP_MSG_DEBUG(("APP_CODEC_LED_UPDATE\n"));
            app_codec_led_update();
        }
        break;

        case APP_POWER_OFF_LED_PLAY_COMPLETED:
        {
            APP_MSG_DEBUG(("APP_POWER_OFF_LED_PLAY_COMPLETED\n"));
            app_power_off_pattern_complete();
        }
        break;

        default:
        {
           APP_MSG_DEBUG(("Unhandled App msg 0x%x\n", id));
        }
        break;
    }
}

/****************************************************************************
NAME    
    app_handler - Handles application messages.
*/
void source_app_handler(Task task, MessageId id, Message message)
{  
    if((id >= APP_INIT_CFM) && (id <APP_INTERNAL_MESSAGE_TOP))
    {
        app_msg_handler(task,id,message);
    }
#ifdef INCLUDE_BUTTONS
    else if((id >= BUTTON_MSG_PAIRING) && (id <= PIO_RAW))
    {
        button_msg_handler(task,id,message);
    }
#endif
    else
    {
        switch (id)
        {
            case AVRCP_CONNECT_CFM:
            {
                APP_MSG_DEBUG(("AVRCP_CONNECT_CFM\n"));
                /* AVRCP connect confirmation can be sent to this handler if no free  
                    AVRCP instances were found during an incoming connection.
                    The message can just be ignored. */
            }
            break;
                    
            default:
            {
                APP_MSG_DEBUG(("APP message unrecognised!\n"));
            }
            break;                
        }
    }
}

/****************************************************************************
NAME    
    source_app_power_device - Latches power on and removes power from the device
*/
void source_app_power_device(bool enable)
{
    PioCommonSetPio(15, 1, FALSE);

    if(!enable)
    {
        /* when shutting down ensure all psus are off for BC7 chips */
        DEBUG_LOG_INFO("PsuConfigure(PSU_ALL, PSU_ENABLE, FALSE)");
        PsuConfigure(PSU_ALL, PSU_ENABLE, FALSE);
    }
}


/****************************************************************************
NAME    
    app_init_cfm - Called when the application has been initialised.
*/
static void app_init_cfm(void)
{
    /* write Class of Device */
    ConnectionWriteClassOfDevice(COD_MAJOR_CAPTURING | COD_MAJOR_AV | COD_MINOR_AV_HIFI_AUDIO);

    /* Set the page scan params */
    ConnectionWritePagescanActivity(0x0800/* 1.28 s */, 0x0012/* 11.25 ms */);

    /* Set the inquiry scan params */
    ConnectionWriteInquiryScanType(hci_scan_type_interlaced);
    ConnectionWriteInquiryscanActivity(0x0200/* 320 ms */, 0x0012/* 11.25 ms */);

    /* Use interlaced scan */
    ConnectionWritePageScanType(hci_scan_type_interlaced);

    if(buttons_power_on() && (appBatteryGetVoltageAverage() > 3300))
    {
        /* must enter idle state initially on power on */
        states_set_state(SOURCE_STATE_IDLE);
    }
    else if(states_get_state() != SOURCE_STATE_POWERED_OFF)
    {
        states_set_state_to_power_off();
    }

    SystemReboot_ResetAction();

    sourcePsSetAutoPowerOn(FALSE);

    if(Charger_IsConnected())
    {
        power_show_charging_led();
    }
}

/****************************************************************************
NAME    
    app_connect_success_cfm - Called when on a successful connection event.
*/
static void app_connect_success_cfm(void)
{
    connection_mgr_set_Profile(TRUE);

    switch (states_get_state())
    {
        case SOURCE_STATE_CONNECTING:
        {
            if (!connection_mgr_connect_next_profile())
            {
                /* go on to attempt connection to a second device if this side is currently initiating the connection */
                if (!connection_mgr_connect_further_device(FALSE))
                {
                    states_set_state(SOURCE_STATE_CONNECTED);
                }
            }
        }
        break;

        case SOURCE_STATE_IDLE:
        case SOURCE_STATE_CONNECTABLE:
        case SOURCE_STATE_DISCOVERABLE:
        case SOURCE_STATE_INQUIRING:
        {
            if (!connection_mgr_connect_next_profile())
            {
                /* move to connected state */
                states_set_state(SOURCE_STATE_CONNECTED);
            }
        }
        break;

        case SOURCE_STATE_POWERED_OFF:
        case SOURCE_STATE_TEST_MODE:
        {
            MessageSend(app_get_instance(), APP_DISCONNECT_REQ, 0);
        }
        break;

#ifdef INCLUDE_DUALSTREAM
        case SOURCE_STATE_CONNECTED:
        {
            MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, connection_mgr_get_audio_delay_timer());
        }
        break;
#endif

        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }

    /* see if scan has to be updated due to new connection */
    scan_check_connection_state();
}


/****************************************************************************
NAME    
    app_connect_fail_cfm - Called when on a failed connection event.
*/
static void app_connect_fail_cfm(void)
{ 
    switch (states_get_state())
    {
        case SOURCE_STATE_CONNECTING:
        {
            if (!connection_mgr_connect_next_profile())
            {
                /* all supported profiles attempted */
                if (!connection_mgr_any_connected_profiles())
                {
                    /* no active profile connections */
                    /* attempt connection to device from paired list */
                    if (!connection_mgr_connect_next_paired_device())
                    {
                        if(connection_mgr_get_connect_policy() == CONNECT_LAST_DEVICE)
                        {
                            if (!connection_mgr_connect_further_device(FALSE))
                            {
                                /* update state */
                                states_set_state(SOURCE_STATE_IDLE);
                            }
                        }
                        else
                        {
                            states_set_state(SOURCE_STATE_IDLE);
                        }
                    }
                }
                else
                {
                    if (!connection_mgr_connect_further_device(FALSE))
                    {
                        /* a profile is still connected so update state */
                        states_set_state(SOURCE_STATE_CONNECTED);

                        app_codec_led_update();
                    }
                }
            }
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }     
    
    /* see if scan has to be updated after failed connection */
    scan_check_connection_state();
}


/****************************************************************************
NAME    
    app_disconnect_ind - Called when on a disconnection event.
*/
static void app_disconnect_ind(const APP_DISCONNECT_IND_T *ind)
{
    if (connection_mgr_any_connected_profiles())
    {
        connection_mgr_set_Profile(TRUE);
    }
    else
    {
        connection_mgr_set_Profile(FALSE);
    }
    
    switch (states_get_state())
    {
        case SOURCE_STATE_CONNECTED:
        {
            /* only change state if all profiles are disconnected */
            if (!connection_mgr_get_profile_connected())
            {
                /* update state */
                states_set_state(SOURCE_STATE_CONNECTABLE);
            }
#ifdef INCLUDE_DUALSTREAM
            else
            {
                if(ind->profile == PROFILE_A2DP)
                {
                    a2dpInstance *inst;
                    /* Still have one device connected , so show one device connected LED */
                    inst = a2dp_get_instance_from_device_id(0);

                    if(inst && !BdaddrIsSame(&ind->addr, &inst->addr))
                    {
                        a2dp_update_codec();
                        break;
                    }

                    inst = a2dp_get_instance_from_device_id(1);

                    if(inst && !BdaddrIsSame(&ind->addr, &inst->addr))
                    {
                        a2dp_update_codec();
                        break;
                    }

                }
            }
#endif
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
    
    /* see if scan has to be updated due to disconnection */
    scan_check_connection_state();
}


/****************************************************************************
NAME    
    app_connect_req - Called to start a connection attempt.
*/
static void app_connect_req(const APP_CONNECT_REQ_T *message)
{
    APP_MSG_DEBUG(("APP_MSG: app_connect_req\n"));

    /* cancel any other connection requests as they may come from user or be automatically generated */
    MessageCancelAll(app_get_instance(), APP_CONNECT_REQ);
    
    switch (states_get_state())
    {
        case SOURCE_STATE_IDLE:
        case SOURCE_STATE_CONNECTABLE:
        case SOURCE_STATE_DISCOVERABLE:
        case SOURCE_STATE_INQUIRING: /* connect after device found through inquiry */
        case SOURCE_STATE_CONNECTING: /* this will occur when trying to connect further profiles - re-enter state to kick connection */
        case SOURCE_STATE_CONNECTED:
        {
#ifndef INCLUDE_DUALSTREAM
            /* suspend any active audio */
            MessageSend(app_get_instance(), APP_AUDIO_SUSPEND, 0);
#endif
            /* connect to remote device */
            if (BdaddrIsZero(connection_mgr_get_remote_address()) || message->force_inquiry_mode)
            {
                states_set_state(SOURCE_STATE_INQUIRING);
            }
            else
            {                
                states_set_state(SOURCE_STATE_CONNECTING);
            }
        }
        break;
               
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
}


/****************************************************************************
NAME    
    app_disconnect_all_req - Called to start a disconnection attempt. If A2DP media is open this will be closed first.
                            Then a APP_DISCONNECT_SIGNALLING_REQ message will be sent.
*/
static void app_disconnect_all_req(void)
{
    uint16 delay = 0;
    
    APP_MSG_DEBUG(("APP_MSG: app_disconnect_all_req\n"));
#ifdef INCLUDE_DUALSTREAM
    if(!BdaddrIsZero(&theSource->app_data.device_to_disconnect))
    {
        /* close A2DP media first then signalling connections in the White Paper disconnection order */
        if (a2dp_disconnect_specified_media(theSource->app_data.device_to_disconnect))
        {
            delay = A2DP_MEDIA_CLOSE_TIMEOUT;
            connection_mgr_set_a2dp_media_before_signalling(TRUE);
        }

        MessageSendLater(app_get_instance(), APP_DISCONNECT_SIGNALLING_REQ, 0, delay);
    }
    else
#endif
    {
        /* close A2DP media first then signalling connections in the White Paper disconnection order */
        if (a2dp_disconnect_media())
        {
            delay = A2DP_MEDIA_CLOSE_TIMEOUT;
            connection_mgr_set_a2dp_media_before_signalling(TRUE);
        }

        MessageSendLater(app_get_instance(), APP_DISCONNECT_SIGNALLING_REQ, 0, delay);
    }
}

/****************************************************************************
NAME    
    app_linkloss_ind - Called when linkloss has occurred and the link_loss_reconnect_delay_timer has expired.
*/
static void app_linkloss_ind(void)
{
    switch (states_get_state())
    {
        case SOURCE_STATE_CONNECTABLE:
        {
            /* If a connection request is queued in the Connectable state then send the connection request now.
                The application should reconnect after the link_loss_reconnect_delay_timer has expired which has now occurred. */
            if (MessageCancelFirst(app_get_instance(), APP_CONNECT_REQ))
            {
                MAKE_MESSAGE(APP_CONNECT_REQ);
                message->force_inquiry_mode = FALSE;
                MessageSend(app_get_instance(), APP_CONNECT_REQ, message);
                APP_MSG_DEBUG(("APP_MSG: Reconnect now due to link loss\n"));
            }
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
}


/****************************************************************************
NAME    
    app_disconnect_signalling_req - Called to disconnect all signalling connections.
*/
static void app_disconnect_signalling_req(void)
{
    APP_MSG_DEBUG(("APP_MSG: app_disconnect_signalling_req\n"));
    
    /* disconnecting signalling now so reset the disconnecting media flag */
    connection_mgr_set_a2dp_media_before_signalling(FALSE);

    if(BdaddrIsZero(&theSource->app_data.device_to_disconnect))
    {
        /* disconnect all profiles - the disconnect order complies with multi profile Whitepapers */

        /* must try to disconnect AVRCP regardless of state */
        avrcp_disconnect_all();
        /* must try to disconnect A2DP regardless of state */
        a2dp_disconnect_all();
        /* must try to disconnect AGHFP regardless of state */
        aghfp_disconnect_all();
    }
    else
    {
        /* must try to disconnect AVRCP regardless of state */
        avrcp_disconnect(theSource->app_data.device_to_disconnect);
        /* must try to disconnect A2DP regardless of state */
        a2dp_disconnect(theSource->app_data.device_to_disconnect);
        /* must try to disconnect AGHFP regardless of state */
        aghfp_disconnect(theSource->app_data.device_to_disconnect);
    }
}

/****************************************************************************
NAME    
    app_discovery_state_timeout - Called after discovery state timer has expired.

*/
static void app_discovery_state_timeout(void)
{
    switch (states_get_state())
    {
        case SOURCE_STATE_DISCOVERABLE:
        {
            /* restart inquiry after discovery state timeout */
            states_set_state(SOURCE_STATE_INQUIRING);
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
}

/****************************************************************************
NAME    
    app_inquiry_continue - Called to continue inquiry
*/
static void app_inquiry_continue(void)
{
    APP_MSG_DEBUG(("APP_MSG: app_inquiry_continue\n"));
    
    switch (states_get_state())
    {
        case SOURCE_STATE_INQUIRING:
        {
            /* continue inquiry */
            inquiry_process_results();
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
}


/****************************************************************************
NAME    
    app_enter_pairing_state_from_idle - Called to move from an idle state into DISCOVERABLE state
*/
static void app_enter_pairing_state_from_idle(void)
{
    switch (states_get_state())
    {
        /* only enter pairing mode if not involved in inquiry/connection */
        case SOURCE_STATE_IDLE:
        case SOURCE_STATE_CONNECTABLE:
        {
            inquiry_complete();
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
}


/****************************************************************************
NAME    
    app_enter_connectable_state_from_idle - Called to move an idle state into CONNECTABLE state
*/
static void app_enter_connectable_state_from_idle(void)
{
    /* only enter connectable mode if not involved in inquiry/connection */
    switch (states_get_state())
    {
        case SOURCE_STATE_IDLE:
        case SOURCE_STATE_DISCOVERABLE:
        {
            states_set_state(SOURCE_STATE_CONNECTABLE);
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
}


/****************************************************************************
NAME    
    app_usb_audio_active - Called when USB audio has been activated
*/
static void app_usb_audio_active(void)
{
    audio_set_usb_active_flag(TRUE);

    /* route audio if connected to a device */
    if (states_get_state() == SOURCE_STATE_CONNECTED)
    {
        MessageSend(app_get_instance(), APP_UPDATE_AUDIO_ROUTING, 0);
    }
}


/****************************************************************************
NAME    
    app_usb_audio_inactive - Called when USB audio has been de-activated
*/
static void app_usb_audio_inactive(void)
{
    audio_set_usb_active_flag(FALSE);

    /* route audio if connected to a device */
    if (states_get_state() == SOURCE_STATE_CONNECTED)
    {
        MessageSend(app_get_instance(), APP_UPDATE_AUDIO_ROUTING, 0);
    }
}


/****************************************************************************
NAME    
    app_mic_audio_active - Called when USB mic audio has been activated
*/
static void app_mic_audio_active(void)
{
#if 0
    if(a2dp_get_number_connections() == 2)
    {
        return;
    }

    audio_set_usb_active_flag(TRUE);

    /* route audio if connected to a device */
    switch (states_get_state())
    {
        case SOURCE_STATE_CONNECTED:
        {
            if ((audio_get_voip_music_mode() != AUDIO_VOIP_MODE) && connection_mgr_is_aghfp_profile_enabled())
            {
                audio_switch_voip_music_mode(AUDIO_VOIP_MODE);
            }
        }
        break;
        
        default:
        {
            app_msg_unhandled_state();
        }
        break;
    }
 #endif
}

/****************************************************************************
NAME
    app_mic_audio_inactive - Called when USB mic audio has been de-activated
*/
static void app_mic_audio_inactive(void)
{

}

static void app_audio_suspend_cfm(void)
{
    if(inquiry_get_forced_inquiry_mode())
    {
        states_set_state(SOURCE_STATE_INQUIRING);
    }

    if(theSource->app_data.audio_source_switching)
    {
        if(a2dp_all_suspended())
        {
            theSource->app_data.audio_source_switching = FALSE;

            app_update_audio_routing(300);
        }
    }

    if(theSource->app_data.codec_switching)
    {
        theSource->app_data.codec_switching = FALSE;

        a2dp_swtich_to_next_priority_codec();
    }
}

/****************************************************************************
NAME
    app_update_audio_routing - update autio routing per audio source priority
*/
static void app_update_audio_routing(uint16 delay)
{
    bool usbAudioActive = audio_get_usb_active_flag();

    MessageCancelAll(app_get_instance(), APP_UPDATE_AUDIO_ROUTING);

    if(states_get_state() == SOURCE_STATE_INITIALISING || !a2dp_get_number_connections())
    {
        return;
    }

    if(!theSource->app_data.audio_source_switching)
    {
        /* route usb audio */
        if(usbAudioActive)
        {
            if(audio_get_input_source() != AUDIO_SOURCE_USB)
            {
                if(audio_is_routing())
                {
                    theSource->app_data.audio_source_switching = TRUE;

                    audio_suspend_all();
                }
                else
                {
                    audio_set_input_source(AUDIO_SOURCE_USB);

                    MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, delay);
                }
            }
            else
            {
                MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, delay);
            }
        }
        else
        {
            if(audio_get_input_source() != AUDIO_SOURCE_ANALOG)
            {
                /* route analog */
                if(audio_is_routing())
                {
                    theSource->app_data.audio_source_switching = TRUE;

                    audio_suspend_all();
                }
                else
                {
                    audio_set_input_source(AUDIO_SOURCE_ANALOG);

                    MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, delay);
                }
            }
            else
            {
                MessageSendLater(app_get_instance(), APP_AUDIO_START, 0, delay);
            }
        }
    }
}

static void app_factory_reset(void)
{
    ConnectionSmDeleteAllAuthDevices(0);

    sourcePsFactoryDefault();

    SystemReboot_RebootWithAction(reboot_action_factory_reset);
}

static void app_a2dp_media_connected(bool codec_configure)
{
    if(!codec_configure && a2dp_allow_more_connections())
    {
        if (!connection_mgr_connect_further_device(TRUE))
        {
            if(states_get_state() != SOURCE_STATE_CONNECTED)
            {
                states_set_state(SOURCE_STATE_CONNECTED);
            }

            app_codec_led_update();
        }
        else
        {
            return;
        }
    }

    app_update_audio_routing(1000);
}

static void app_codec_swtich(void)
{
    if(a2dp_get_number_connections() && audio_is_routing())
    {
        MessageCancelAll(app_get_instance(), APP_CODEC_SWITCH);

        if(audio_get_audio_routed() == AUDIO_ROUTED_A2DP)
        {
            audio_suspend_all();

            theSource->app_data.codec_switching = TRUE;
        }
        else
        {
            a2dp_swtich_to_next_priority_codec();
        }

        theSource->app_data.block_led_update_during_codec_switch = TRUE;
    }
}

static void app_volume_up(void)
{
    uint8 volume;
    AUDIO_SOURCE_T sourceType = audio_get_input_source();

    switch(sourceType)
    {
    case AUDIO_SOURCE_ANALOG:
    case AUDIO_SOURCE_SPDIF:
    case AUDIO_SOURCE_I2S:
        avrcp_volume_up();
        break;
    case AUDIO_SOURCE_USB:
        volume = usbGetHeadsetSpeakerVolume();
        
        if(volume < 15)
        {
            usbSetDeviceSpeakerVolume(volume + 1);

            if(a2dp_get_number_connections() > 0)
            {
                avrcp_send_source_volume(NULL, volume + 1, FALSE);
            }
        }
        break;
    }
}

static void app_volume_down(void)
{
    uint8 volume;
    AUDIO_SOURCE_T sourceType = audio_get_input_source();

    switch(sourceType)
    {
    case AUDIO_SOURCE_ANALOG:
    case AUDIO_SOURCE_SPDIF:
    case AUDIO_SOURCE_I2S:
        avrcp_volume_down();
        break;
    case AUDIO_SOURCE_USB:

        volume = usbGetHeadsetSpeakerVolume();
        
        if(volume > 0)
        {
            usbSetDeviceSpeakerVolume(volume - 1);

            if(a2dp_get_number_connections() > 0)
            {
                avrcp_send_source_volume(NULL, volume - 1, FALSE);
            }
        }
        break;
    }
}

static void app_auto_power_off(void)
{
    if((!a2dp_get_number_connections()) && (states_get_state() != SOURCE_STATE_POWERED_OFF))
    {
        states_set_state(SOURCE_STATE_POWERED_OFF);
    }
}

static void app_next_track(void)
{
    usbHidSendMediaCommand(USB_HID_MEDIA_NEXT_TRACK);
}

static void app_previous_track(void)
{
    usbHidSendMediaCommand(USB_HID_MEDIA_PREVIOUS_TRACK);
}

static void app_codec_led_update(void)
{
    uint16 devices = a2dp_get_number_connections();
    a2dpInstance *inst;

    if(devices == 1)
    {
        if(audio_is_routing())
        {
            /* Still have one device connected , so show one device connected LED */
            inst = a2dp_get_instance_from_device_id(0);

            if(inst)
            {
                if(BdaddrIsSame(&theSource->app_data.primary_addr, &inst->addr))
                {
                    led_show_codec(TRUE, inst->seid);
                }
                else if(BdaddrIsSame(&theSource->app_data.secondary_addr, &inst->addr))
                {
                    led_show_codec(FALSE, inst->seid);
                }
            }
        }
        else
        {
            if(!theSource->app_data.block_led_update_during_codec_switch)
                leds_show_event(LED_EVENT_ONE_DEVICE_CONNECTED);
        }
    }
    else if(devices == 2)
    {
        if(audio_is_routing())
        {
            /* Still have one device connected , so show one device connected LED */
            inst = a2dp_get_instance_from_device_id(0);

            if(inst)
            {
                if(BdaddrIsSame(&theSource->app_data.primary_addr, &inst->addr))
                {
                    led_show_codec(TRUE, inst->seid);
                }
                else if(BdaddrIsSame(&theSource->app_data.secondary_addr, &inst->addr))
                {
                    led_show_codec(FALSE, inst->seid);
                }
            }
        }
        else
        {
            if(!theSource->app_data.block_led_update_during_codec_switch)
                leds_show_event(LED_EVENT_TWO_DEVICES_CONNECTED);
        }
    }
}

static void app_avrcp_play(void)
{
    audio_route_all();
}

static void app_avrcp_stop(void)
{
    a2dp_suspend_all_audio();
}

static void app_avrcp_toggle(void)
{
    if(a2dp_audio_is_streaming())
    {
        a2dp_suspend_all_audio();
    }
    else
    {
        audio_route_all();
    }
}

static void app_power_off_pattern_complete(void)
{
    if(Charger_IsConnected())
    {
        power_show_charging_led();
    }
}

void source_app_refresh_auto_power_off_timer(void)
{
    MessageCancelAll(app_get_instance(), APP_AUTO_POWER_OFF);
    MessageSendLater(app_get_instance(), APP_AUTO_POWER_OFF, 0, D_MIN(10));
}

void source_app_cancel_auto_power_off(void)
{
    MessageCancelAll(app_get_instance(), APP_AUTO_POWER_OFF);
}
